package com.yigerende.service.impl;

import com.fasterxml.jackson.databind.util.BeanUtil;
import com.yigerende.NotFoundException;
import com.yigerende.dao.BlogRepository;
import com.yigerende.model.Blog;

import com.yigerende.model.Type;
import com.yigerende.service.BlogService;
import com.yigerende.util.MarkdownUtils;
import com.yigerende.vo.BlogQuery;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;


import javax.persistence.criteria.*;
import java.util.*;

@Service
public class BlogServiceImpl implements BlogService {


    @Autowired
    private BlogRepository blogRepository;

    //显示所有博客(通过id查询博客)
    @Override
    public Blog getBlog(Long id) {
        return blogRepository.findById(id).get();
    }

    @Transactional
    @Override
    public Blog getAndConvert(Long id) {
        Blog blog = blogRepository.findById(id).get();
        if(blog == null){
            throw new NotFoundException("该博客不存在");
        }
        //防止数据库里面的内容也改变
        Blog b = new Blog();
        BeanUtils.copyProperties(blog,b);//把原来的内容先放到blog里面

        String content = b.getContent();//取到博客的内容
        b.setContent(MarkdownUtils.markdownToHtmlExtensions(content));//用工具类把文本给解析出来


        //每一次访问都会调用一次这个方法，每次调用，views查看次数都会+1
        blogRepository.updateViews(id);

        return b;
    }


    //分页查询(传递一个分页对象，还要传递一个博客对象用来选择性查询)
    @Override
    public Page<Blog> listBlog(Pageable pageable, BlogQuery blog) {
        /*选择性查询以前就是判断用户有一个输入框没有输入或者两个输入框都有输入的不同查询
        现在JPA给我们封装好了这种高级查询*/
        return blogRepository.findAll(new Specification<Blog>() {//new Spec回车
            @Override
            public Predicate toPredicate(Root<Blog> root, CriteriaQuery<?> cq, CriteriaBuilder cb) {
                List<Predicate> predicates = new ArrayList<>();
                //进行判断第一个输入框(标题)用户有没有输入，是不是空
                if(!"".equals(blog.getTitle()) && blog.getTitle() != null){//不等于空(说明用户在标题哪里有写的)
                    //有标题传递到后面的话
                    /*
                    * 第一步cb.like(root.<String>get("title")拿到标题的类型String
                    * 第二步"%"+blog.getTitle()+"%"把用户输入的标题传递给前面的类型
                    * */
                    predicates.add(cb.like(root.<String>get("title"),"%"+blog.getTitle()+"%"));
                }
                //进行判断第二个输入框(分类)用户有没有选择,是不是空
                //选择了博客，分类选择，显示对应有的分类
                if(blog.getTypeId() != null){
                    /*
                    * 使用equal，这里是放入对象root.<Type>get("type")，取到里面的id,get("id")
                    * 第一步：定义类型root.<Type>get("type").get("id")
                    * 第二步：传递值blog.getType().getId()
                    * */
                    predicates.add(cb.equal(root.<Type>get("type").get("id"),blog.getTypeId()));
                }
                //用户是否选择推荐
                //因为这是个复选框，判断是否为true就可以了,blog.isRecommend()选中了本身就为true
                if(blog.isRecommend()){
                    /*
                    * 第一步：放入对象类型，布尔型root.<Boolean>get("recommend")
                    * 第二步：传递真假给前面的类型blog.isRecommend()
                    * */
                    predicates.add(cb.equal(root.<Boolean>get("recommend"),blog.isRecommend()));
                }
                cq.where(predicates.toArray(new Predicate[predicates.size()]));
                //上面的就是将用户选择查询的数据进行拼接，来查询
                return null;
            }
        },pageable);
        //传入两个对象，第一个高级处理Specifcation，第二个分页对象
    }




    //新增博客(传递用户输入的博客，从数据库查询到返回博客)
    @Transactional//放到事物管理里面
    @Override
    public Blog saveBlog(Blog blog) {
        if(blog.getId() == null){//增加
            //创建时间初始化
            blog.setCreateTime(new Date());
            //更新时间初始化
            blog.setUpdateTime(new Date());
            //浏览次数初始化
            blog.setViews(24);
        }else{//修改的话只需要改更新时间
            //更新时间初始化
            blog.setUpdateTime(new Date());
        }
        return blogRepository.save(blog);
    }


    //修改博客(通过id修改博客)
    @Transactional
    @Override
    public Blog updateBlog(Long id, Blog blog) {
        //通过id查询到数据，进行判断
        Blog b = blogRepository.findById(id).get();
        //判断要修改的分类不在的话
        if(b == null){
            throw new NotFoundException("不存在该类型");//自定义异常显示
        }
        //用BeanUtils来把数据库查到的blog数据赋值给b模型
        BeanUtils.copyProperties(blog,b);

        //直接把Type模型使用增加的方法save修改，因为t里面有id，所以是修改
        return blogRepository.save(b);
    }

    //删除博客(通过id来删除博客)
    @Transactional
    @Override
    public void deleteBlog(Long id) {
        blogRepository.deleteById(id);
    }

    //查询到全部数据
    @Override
    public List<Blog> listBlog() {
        return blogRepository.findAll();
    }










    /*上面是管理页面的，下面是用户显示页*/


    //分页查询所有博客
    @Override
    public Page<Blog> listBlog(Pageable pageable) {
        return (Page<Blog>) blogRepository.findAll(pageable);
    }

    //最新推荐显示
    @Override
    public List<Blog> listRecommendBlogTop(Integer size) {
        /**
         * 这里是实现查询排序所以要这样写
         * 现在的版本这两个方法直接调用即可，不要创建对象
         * */
        //根据更新时间进行倒序排列
        Sort sort = Sort.by(Sort.Direction.DESC,"updateTime");
        //创建分页对象，给第一页，size一页显示多少条分类，sort根据上面排序
        Pageable pageable = PageRequest.of(0,size,sort);
        return blogRepository.findTop(pageable);
    }


    //全局搜索
    @Override
    public Page<Blog> listBlog(String query, Pageable pageable) {
        return blogRepository.findByQuery(query,pageable);
    }



    //归档查询所有数据
    /**
     * 先查询出所有博客存在的年份
     * 在用年份查询出对应的数据，存在map集合里面
     * */
    @Override
    public Map<String, List<Blog>> archiveBlog() {
        //创建一个map集合，用于保存
        Map<String,List<Blog>> map = new HashMap<>();
        List<String> years = blogRepository.findGroupYear();
        //拿到了有的年份，进行循环出来找到对应年份的博客
        for (String year : years){
            map.put(year,blogRepository.findByYear(year));//第一个给年份，第二个给对应年份的数据(通过年份来查询)
        }
        return map;
    }

    //归档后的条数
    @Override
    public Long countBlog() {
        return blogRepository.count();//直接调用这个方法，就会把blog里面的条数返回
    }


    //通过标签点击查询
    @Override
    public Page<Blog> listBlog(Long tagId, Pageable pageable) {
        return blogRepository.findAll(new Specification<Blog>() {//new Spec回车
            @Override
            //3张表连接，通过传递的标签id，关联显示
            public Predicate toPredicate(Root<Blog> root, CriteriaQuery<?> cq, CriteriaBuilder cb) {
                Join join = root.join("tags");
                return cb.equal(join.get("id"),tagId);
            }
        },pageable);
    }
}
